/**
 * This file is part of the NatCrackerToolbox.
 * 
 * Copyright (C) 2009 - 2011 Peerialism Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **/
package com.peerialism.natcracker.common;

import java.io.Serializable;
import java.net.InetAddress;
import java.util.StringTokenizer;

public class GatewayDevice implements Serializable {
	private static final long serialVersionUID = -7946945601070132185L;

	// DEFINITIONS
	public enum GatewayType {
		// The types must always be ordered from the best to the worst type in
		// order to get correct results in the comparator
		OpenInternet, NAT, UdpBlockingFirewall
	}

	// Instance
	private GatewayType gatewayType;
	private PortAllocationPolicy portAllocationPolicy;
	private PortMappingPolicy portMappingPolicy;
	private FilteringPolicy portFilteringPolicy;
	private ClashingPolicy clashingPolicy;

	public GatewayDevice() {
		gatewayType = GatewayType.UdpBlockingFirewall;
		portAllocationPolicy = PortAllocationPolicy.RANDOM;
		portMappingPolicy = PortMappingPolicy.ENDPOINT_INDEPENDENT;
		portFilteringPolicy = FilteringPolicy.ENDPOINT_INDEPENDENT;
		clashingPolicy = ClashingPolicy.OPENS;
	}

	private int portIncrement = 0;
	private InetAddress publicAddress;
	private boolean isSHPMultisession;

	public int getPortIncrement() {
		return portIncrement;
	}

	public void setPortIncrement(final int portIncrement) {
		this.portIncrement = portIncrement;
	}

	public PortAllocationPolicy getPortAllocationPolicy() {
		return portAllocationPolicy;
	}

	public void setPortAllocationPolicy(
			final PortAllocationPolicy portMappingTechnique) {
		portAllocationPolicy = portMappingTechnique;
	}

	public PortMappingPolicy getPortMappingPolicy() {
		return portMappingPolicy;
	}

	public void setPortMappingPolicy(
			final PortMappingPolicy portAllocationPolicy) {
		portMappingPolicy = portAllocationPolicy;
	}

	public FilteringPolicy getPortFilteringPolicy() {
		return portFilteringPolicy;
	}

	public void setPortFilteringPolicy(final FilteringPolicy portFilteringPolicy) {
		this.portFilteringPolicy = portFilteringPolicy;
	}

	public ClashingPolicy getClashingPolicy() {
		return clashingPolicy;
	}

	public void setPortClashingPolicy(final ClashingPolicy clashingPolicy) {
		this.clashingPolicy = clashingPolicy;
	}

	public String toCombination() {
		return portMappingPolicy.toString() + ":"
				+ portFilteringPolicy.toString() + ":"
				+ portAllocationPolicy.toString();
	}

	public String toLongString() {
		if (gatewayType != GatewayType.NAT)
			return gatewayType.name();
		else
			return "Mapping: "
					+ portMappingPolicy.toString()
					+ ", Filtering:"
					+ portFilteringPolicy.toString()
					+ ", Allocation:"
					+ portAllocationPolicy.toString()
					+ (portAllocationPolicy == PortAllocationPolicy.PORT_CONTIGUITY ? "(P="
							+ portIncrement + ")"
							: "")
					+ (portAllocationPolicy == PortAllocationPolicy.PORT_PRESERVATION
							&& portMappingPolicy == PortMappingPolicy.PORT_DEPENDENT ? ", Clashing:"
							+ clashingPolicy.toString()
							: "");
	}

	@Override
	public String toString() {
		if (gatewayType != GatewayType.NAT)
			return gatewayType.name();
		else
			return gatewayType
					+ "=[MAP:"
					+ shorten(portMappingPolicy.toString(), "_")
					+ ",FLT:"
					+ shorten(portFilteringPolicy.toString(), "_")
					+ ",PRED:"
					+ shorten(portAllocationPolicy.toString(), "_")
					+ (portAllocationPolicy == PortAllocationPolicy.PORT_CONTIGUITY ? "(P="
							+ portIncrement + ""
							: "") + "]";
	}

	public String shortString() {
		if (gatewayType != GatewayType.NAT)
			return gatewayType.name();
		else
			return gatewayType + "[M:"
					+ shorten(portMappingPolicy.toString(), "_") + "-F:"
					+ shorten(portFilteringPolicy.toString(), "_") + "-P:"
					+ shorten(portAllocationPolicy.toString(), "_") + "]";
	}

	public static String shorten(final String str, final String del) {
		final StringTokenizer tokenizer = new StringTokenizer(str, del);
		String resStr = "";
		for (; tokenizer.hasMoreTokens();) {
			final String tokString = tokenizer.nextToken();
			resStr += tokString.substring(0, 1);
			if (tokenizer.hasMoreTokens())
				resStr += del;
		}
		return resStr;
	}

	public GatewayType getGatewayType() {
		return gatewayType;
	}

	public void setGatewayType(final GatewayType gatewayType) {
		this.gatewayType = gatewayType;
	}

	public boolean isUDPBlocked() {
		if (gatewayType == GatewayType.UdpBlockingFirewall)
			return true;
		return false;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((gatewayType == null) ? 0 : gatewayType.hashCode());
		if ((gatewayType != null) && (gatewayType.equals(GatewayType.NAT))) {
			result = prime
					* result
					+ ((portFilteringPolicy == null) ? 0 : portFilteringPolicy
							.hashCode());
			result = prime
					* result
					+ ((portMappingPolicy == null) ? 0 : portMappingPolicy
							.hashCode());
			result = prime
					* result
					+ ((portAllocationPolicy == null) ? 0
							: portAllocationPolicy.hashCode());
		}
		return result;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final GatewayDevice other = (GatewayDevice) obj;
		if (gatewayType == null) {
			if (other.gatewayType != null)
				return false;
		} else if (!gatewayType.equals(other.gatewayType))
			return false;
		if (!(gatewayType.equals(GatewayType.NAT))
				&& gatewayType.equals(other.gatewayType))
			return true;
		if (portFilteringPolicy == null) {
			if (other.portFilteringPolicy != null)
				return false;
		} else if (!portFilteringPolicy.equals(other.portFilteringPolicy))
			return false;
		if (portMappingPolicy == null) {
			if (other.portMappingPolicy != null)
				return false;
		} else if (!portMappingPolicy.equals(other.portMappingPolicy))
			return false;
		if (portAllocationPolicy == null) {
			if (other.portAllocationPolicy != null)
				return false;
		} else if (!portAllocationPolicy.equals(other.portAllocationPolicy))
			return false;
		return true;
	}

	public void setGatewayIp(final InetAddress publicAddress) {
		this.publicAddress = publicAddress;
	}

	public InetAddress getGatewayIp() {
		return publicAddress;
	}

	public GatewayDevice cloneAsNormalNat() throws CloneNotSupportedException {
		GatewayDevice gtw = new GatewayDevice();
		gtw.setGatewayType(GatewayType.NAT);
		gtw.setPortAllocationPolicy(getPortAllocationPolicy());
		gtw.setPortFilteringPolicy(getPortFilteringPolicy());
		gtw.setPortMappingPolicy(getPortMappingPolicy());
		return gtw;
	}

	public String export() {
		StringBuilder builder = new StringBuilder();
		builder.append(gatewayType);
		if (gatewayType == GatewayType.NAT)
			builder.append("|" + portMappingPolicy.toString() + "|"
					+ portFilteringPolicy.toString() + "|"
					+ portAllocationPolicy.toString());
		return builder.toString();
	}

	public static GatewayDevice createFromString(String description) {
		final StringTokenizer tokenizer = new StringTokenizer(description,
				"|\\s");
		String str = null;
		int i = 1;
		GatewayDevice dev = new GatewayDevice();
		try {
			GatewayType type = GatewayType.valueOf(tokenizer.nextToken());
			dev.setGatewayType(type);
			if (tokenizer.countTokens() >= 1)
				for (; tokenizer.hasMoreTokens();) {
					str = tokenizer.nextToken();
					switch (i) {
					case 1:
						dev
								.setPortMappingPolicy(PortMappingPolicy
										.valueOf(str));
						break;
					case 2:
						dev
								.setPortFilteringPolicy(FilteringPolicy
										.valueOf(str));
						break;
					case 3:
						dev.setPortAllocationPolicy(PortAllocationPolicy
								.valueOf(str));
						break;
					default:
						break;
					}
					i++;
				}
		} catch (Exception e) {
			dev = null;
		}
		return dev;
	}

	public static void main(String[] args) {
		GatewayDevice dev = new GatewayDevice();
		dev.setGatewayType(GatewayType.NAT);
		String exp = dev.export();
		System.out.println(exp);
		GatewayDevice imp = GatewayDevice.createFromString(exp);
		System.out.println(imp.toLongString());
	}

	public void setSHPMultisessionSupported(boolean isSHPMultisession) {
		this.isSHPMultisession = isSHPMultisession;
	}

	public boolean isSHPMultisession() {
		return isSHPMultisession;
	}
}
